package com.malty.gateway.filter;

import com.malty.common.ErrorCode;
import com.malty.common.api.InnerApiMetaDataService;
import com.malty.common.api.InnerUserInfoService;
import com.malty.common.domain.entity.ApiMetaData;
import com.malty.common.domain.entity.UserInfo;
import com.malty.common.exception.BusinessException;
import com.malty.gateway.config.GatewayContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.rpc.RpcContext;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import static com.malty.gateway.config.constant.GateWayConstant.GATEWAY_CONTEXT;

/**
 * 获取请求内容.
 *
 * @author malty
 */
@Slf4j
@Component
public class GatewayContextFilter implements GlobalFilter, Ordered {
    @DubboReference(async = true)
    private InnerApiMetaDataService apiMetaDataService;

    @DubboReference(async = true)
    private InnerUserInfoService userInfoService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Map<String, Object> attributes = exchange.getAttributes();
        GatewayContext context = (GatewayContext) attributes.get(GATEWAY_CONTEXT);

        if (context != null) {
            return chain.filter(exchange);
        }

        ServerHttpRequest request = exchange.getRequest();

        // 用户凭证校验
        return assertUserAccessKeyAsync(request)
                .flatMap(userInfo ->
                        // 接口校验
                        validateApiAsync(request)
                                .map(apiMetaData -> GatewayContext.generateContext(request, userInfo, apiMetaData.getId()))
                )
                .doOnNext(ctx -> attributes.put(GATEWAY_CONTEXT, ctx))
                .flatMap(ctx -> chain.filter(exchange))
                .onErrorResume(BusinessException.class, e -> apiErrorResponse(exchange.getResponse(), e.getMessage()));
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE + 1;
    }

    private Mono<UserInfo> assertUserAccessKeyAsync(ServerHttpRequest httpRequest) {
        String accessKey = httpRequest.getHeaders().getFirst("access-key");
        if (accessKey == null) {
            return Mono.error(new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "用户信息错误"));
        }

        // 发起异步调用
        userInfoService.getUser(accessKey);
        CompletableFuture<UserInfo> cf = RpcContext.getContext().getCompletableFuture();
        return Mono.fromFuture(cf).flatMap(userInfo -> {
            if (userInfo == null) {
                return Mono.error(new BusinessException(ErrorCode.FORBIDDEN_ERROR, "用户信息错误"));
            }
            return Mono.just(userInfo);
        });
    }

    private Mono<ApiMetaData> validateApiAsync(ServerHttpRequest request) {
        String path = request.getPath().toString();
        String method = request.getMethod().name();
        String accessKey = request.getHeaders().getFirst("access-key");
        apiMetaDataService.getInterfaceInfo(path, method, accessKey);
        CompletableFuture<ApiMetaData> cf = RpcContext.getContext().getCompletableFuture();
        return Mono.fromFuture(cf).flatMap(Mono::just);
    }

    /**
     * 自定义返回 401 错误
     */
    private Mono<Void> apiErrorResponse(ServerHttpResponse response, String message) {
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        String body = String.format("{\"code\": %d, \"message\": \"%s\"}", ErrorCode.NO_AUTH_ERROR.getCode(), message);
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
}